#ifndef OPERATOR_H
#define OPERATOR_H

#include "sqlite3.h"
#include "functions.h"
#include "entitys.h"
#include <string>

using std::string;

namespace Dao {
    class BaseDao;
    class ValueDao;
    class ConnectDao;

    class BaseDao
    {
    protected:
        string url;
        sqlite3* source = nullptr;
        bool isopen = false;
    public:
        void init(string url);
        void init(BaseDao& dao);
        void destroy();
        explicit BaseDao() {}
        explicit BaseDao(string url);
        explicit BaseDao(BaseDao& dao);
        ~BaseDao();
        static BaseDao& copy(BaseDao& dao);
        BaseDao& operator=(BaseDao& dao);
        string getUrl() const;
        sqlite3* getSource() const;
        void execute(string sql) const;
        void begin() const;
        void commit() const;
        void rollback() const;
    };

    inline void BaseDao::init(string url)
    {
        const int rc = sqlite3_open(url.c_str(), &this->source);
        if (rc != SQLITE_OK)
        {
            printf("Can't open database: %s\n", sqlite3_errmsg(this->source));
            sqlite3_close(this->source);
            this->source = nullptr;
            throw "sql error: can't open database.\n";
        }
        else
        {
            this->url = url;
            this->isopen = true;
        }
    }

    inline void BaseDao::init(BaseDao & dao)
    {
        this->url = dao.url;
        this->source = dao.source;
        this->isopen = dao.isopen;
    }

    inline void BaseDao::destroy()
    {
        sqlite3_close(this->source);
        this->url = "";
        this->source = nullptr;
        this->isopen = false;
    }

    inline BaseDao::BaseDao(string url)
    {
        init(url);
    }

    inline BaseDao::BaseDao(BaseDao & dao)
    {
        init(dao);
    }

    inline BaseDao::~BaseDao()
    {
        destroy();
    }

    inline BaseDao& BaseDao::copy(BaseDao& dao)
    {
        return *new BaseDao(dao.url);
    }

    inline BaseDao& BaseDao::operator=(BaseDao& dao)
    {
        if (isopen)
        {
            destroy();
        }
        this->url = dao.url;
        this->source = dao.source;
        this->isopen = dao.isopen;
        return *this;
    }

    inline string BaseDao::getUrl() const
    {
        return this->url;
    }

    inline sqlite3 * BaseDao::getSource() const
    {
        return this->source;
    }

    inline void BaseDao::execute(string sql) const
    {
        char* zErrMsg = nullptr;
        string s = func::ascii_to_utf8(sql);
        printf("%s\n", s.c_str());
        int rc = sqlite3_exec(this->source, s.c_str(), nullptr, nullptr, &zErrMsg);
        if (rc != SQLITE_OK)
        {
            printf("sql error: %s\n", zErrMsg);
            sqlite3_free(zErrMsg);
            throw "sql error: execution error.\n";
        }
    }

    inline void BaseDao::begin() const
    {
        execute("begin;");
    }

    inline void BaseDao::commit() const
    {
        execute("commit;");
    }

    inline void BaseDao::rollback() const
    {
        execute("rollback;");
    }

    class ValueDao final : public BaseDao
    {
        entity::Result<entity::KeyValue> select(string db, string con) const;
        entity::Result<entity::KeyValue> Delete(string db, string con) const;
    public:
        void init(BaseDao& dao);
        void destroy();
        explicit ValueDao() {}
        explicit ValueDao(string url);
        explicit ValueDao(BaseDao& dao);
        ~ValueDao();

        entity::KeyValue select_id(string db, int id) const;
        entity::KeyValue select_value(string db, string value) const;
        entity::Result<entity::KeyValue> select_like(string db, string value) const;

        entity::KeyValue insert(string db, string value, int ref = 0) const;

        entity::KeyValue Delete_id(string db, int id) const;
        entity::KeyValue Delete_value(string db, string value) const;
        entity::Result<entity::KeyValue> Delete_like(string db, string value) const;

        entity::KeyValue update(string db, string oldValue, string newValue) const;
        entity::KeyValue update_id(string db, int old_id, int new_id) const;
        entity::KeyValue update_value(string db, int id, string value) const;
        entity::KeyValue update_ref(string db, int id, int ref);
        entity::KeyValue addref(string db, int id);
        entity::KeyValue subref(string db, int id);
    };

    inline ValueDao::ValueDao(string url) : BaseDao(url) {}

    inline ValueDao::ValueDao(BaseDao & dao)
    {
        BaseDao::init(dao);
    }

    inline ValueDao::~ValueDao() {}

    inline entity::Result<entity::KeyValue> ValueDao::select(string db, string con) const
    {
        char* pzTail;
        sqlite3_stmt* stmt = nullptr;
        string sql = "select * from " + db;
        if (con != "")
        {
            sql += " where " + con;
        }
        sql += +';';
        string s = func::ascii_to_utf8(sql);
        printf("%s\n", s.c_str());
        entity::Result<entity::KeyValue> res;
        if (sqlite3_prepare_v2(this->source, s.c_str(), -1, &stmt, const_cast<const char **>(&pzTail)) == SQLITE_OK)
        {
            while (sqlite3_step(stmt) == SQLITE_ROW)
            {
                entity::KeyValue temp;
                temp.id = sqlite3_column_int(stmt, 0);
                temp.value = string((char*)sqlite3_column_text(stmt, 1));
                temp.value = func::utf8_to_ascii(temp.value);
                temp.ref = sqlite3_column_int(stmt, 2);
                res.push_back(temp);
            }
        }
        else
        {
            printf("sql error: %s\n", pzTail);
            sqlite3_free(pzTail);
            throw "sql error: select error.\n";
        }
        sqlite3_finalize(stmt);
        func::unique(res.get_result());
        return res;
    }

    inline entity::KeyValue ValueDao::select_id(string db, int id) const
    {
        return select(db, "id=" + func::toString(id)).toElement();
    }

    inline entity::KeyValue ValueDao::select_value(string db, string value) const
    {
        return select(db, "value='" + value + '\'').toElement();
    }

    inline entity::Result<entity::KeyValue> ValueDao::select_like(string db, string value) const
    {
        return select(db, "value like '%" + value + "%'");
    }

    inline entity::KeyValue ValueDao::insert(string db, string value, int ref) const
    {
        if (select(db, "value='" + value + '\'').size() == 0)
        {
            execute("insert into " + db + " (value,ref) values('" + value + "'," + func::toString(ref) + ");");
        }
        return select_value(db, value);
    }

    inline entity::Result<entity::KeyValue> ValueDao::Delete(string db, string con) const
    {
        entity::Result<entity::KeyValue> res = select(db, con);
        auto stack = res.get_result();
        for (auto& kv : stack)
        {
            if (kv.ref > 0)
                throw "contains already referenced values.\n";
        }
        execute("delete from " + db + " where " + con + ';');
        return res;
    }

    inline void ValueDao::init(BaseDao & dao)
    {
        BaseDao::init(dao);
    }

    inline void ValueDao::destroy()
    {
        BaseDao::destroy();
    }

    inline entity::KeyValue ValueDao::Delete_id(string db, int id) const
    {
        return Delete(db, "id=" + func::toString(id)).toElement();
    }

    inline entity::KeyValue ValueDao::Delete_value(string db, string value) const
    {
        return Delete(db, "value='" + value + '\'').toElement();
    }

    inline entity::Result<entity::KeyValue> ValueDao::Delete_like(string db, string value) const
    {
        return Delete(db, "value like '%" + value + "%'");
    }

    inline entity::KeyValue ValueDao::update(string db, string oldValue, string newValue) const
    {
        if (select(db, "value='" + oldValue + '\'').size() == 1)
        {
            if (select(db, "value='" + newValue + '\'').size() == 0)
            {
                execute("update " + db + " set value='" + newValue + "' where value='" + oldValue + "';");
            }
            else
            {
                throw newValue + " is in " + db + ".\n";
            }
        }
        else
        {
            throw "can't find value equal to " + oldValue + ".\n";
        }
        return select_value(db, newValue);
    }

    inline entity::KeyValue ValueDao::update_id(string db, int old_id, int new_id) const
    {
        execute("update " + db + " set id=" + func::toString(new_id) + " where id=" + func::toString(old_id) + ";");
        return select_id(db, new_id);
    }

    inline entity::KeyValue ValueDao::update_value(string db, int id, string value) const
    {
        execute("update " + db + " set value='" + value + "' where id=" + func::toString(id) + ";");
        return select_id(db, id);
    }

    inline entity::KeyValue ValueDao::addref(string db, int id)
    {
        execute("update " + db + " set ref=ref+1 where id=" + func::toString(id) + ";");
        return select_id(db, id);
    }

    inline entity::KeyValue ValueDao::subref(string db, int id)
    {
        execute("update " + db + " set ref=ref-1 where id=" + func::toString(id) + ";");
        return select_id(db, id);
    }

    inline entity::KeyValue ValueDao::update_ref(string db, int id, int ref)
    {
        execute("update " + db + " set ref=" + func::toString(ref) + " where id=" + func::toString(id) + ";");
        return select_id(db, id);
    }

    class ConnectDao final : public BaseDao
    {
        ValueDao* vdao;
        entity::Result<entity::Connect> _delete(string con);
    public:
        void init(BaseDao& dao);
        void setValueDao(ValueDao& dao);
        ValueDao* getValueDao() const;
        void destroy();
        explicit ConnectDao() {}
        explicit ConnectDao(string url);
        explicit ConnectDao(BaseDao& dao);
        ~ConnectDao();

        entity::Result<entity::Connect> select(string con) const;
        entity::Connect select_id(int id) const;
        entity::Result<entity::Connect> select_value(vector<int> keys, vector<int> accounts, int password) const;
        entity::Result<entity::Connect> select_like(vector<int> keys, vector<int> accounts, vector<int> passwords) const;

        entity::Connect insert(vector<int> keys, vector<int> accounts, int password) const;
        entity::Connect insert_connect(int id, vector<int> keys, vector<int> accounts) const;

        entity::Connect Delete_id(int id);
        entity::Connect Delete_connect(int id, vector<int> keys, vector<int> accounts);

        entity::Connect update(int id, string field, int old_id, int new_id);
        entity::Connect update_key(int id, int old_id, int new_id);
        entity::Connect update_account(int id, int old_id, int new_id);
        entity::Connect update_password(int id, int new_id);
    };

    inline ConnectDao::ConnectDao(string url) : BaseDao(url) {}

    inline ConnectDao::ConnectDao(BaseDao & dao)
    {
        BaseDao::init(dao);
    }

    inline ConnectDao::~ConnectDao() {}

    inline entity::Result<entity::Connect> ConnectDao::select(string con) const
    {
        char* pzTail = nullptr;
        sqlite3_stmt* stmt = nullptr;
        string sql = "select * from record";
        if (con != "")
        {
            sql += " where " + con;
        }
        sql += +';';
        string s = func::ascii_to_utf8(sql);
        printf("%s\n", s.c_str());
        entity::Result<entity::Connect> res;
        if (sqlite3_prepare_v2(this->source, s.c_str(), -1, &stmt, const_cast<const char **>(&pzTail)) == SQLITE_OK)
        {
            while (sqlite3_step(stmt) == SQLITE_ROW)
            {
                entity::Connect temp;
                temp.id = sqlite3_column_int(stmt, 0);
                temp.keys = string((char*)sqlite3_column_text(stmt, 1));
                temp.keys = func::utf8_to_ascii(temp.keys);
                temp.accounts = string((char*)sqlite3_column_text(stmt, 2));
                temp.accounts = func::utf8_to_ascii(temp.accounts);
                temp.password = sqlite3_column_int(stmt, 3);
                res.push_back(temp);
            }
        }
        else
        {
            printf("sql error: %s\n", pzTail);
            sqlite3_free(pzTail);
            throw "sql error: select error.\n";
        }
        sqlite3_finalize(stmt);
        func::unique(res.get_result());
        return res;
    }

    inline entity::Connect ConnectDao::select_id(int id) const
    {
        return select("id=" + func::toString(id)).toElement();
    }

    inline entity::Result<entity::Connect> ConnectDao::select_value(vector<int> keys, vector<int> accounts, int password = -1) const
    {
        func::unique(keys);
        func::unique(accounts);
        auto _keys = func::encode(keys);
        auto _accounts = func::encode(accounts);
        auto _password = func::toString(password);
        return select("key like '%" + _keys + "%' and account like '%" + _accounts + "%' and password=" + _password);
    }

    inline entity::Result<entity::Connect> ConnectDao::select_like(vector<int> keys, vector<int> accounts, vector<int> passwords) const
    {
        string sql;
        if (keys.size() > 0)
        {
            func::unique(keys);
            sql += "(";
            for (auto& key : keys)
            {
                sql += "key like '% " + func::toString(key) + "%' or ";
            }
            sql.replace(sql.size() - 3, 3, ")");
        }
        if (accounts.size() > 0)
        {
            func::unique(accounts);
            sql += "(";
            for (auto& account : accounts)
            {
                sql += "account like '% " + func::toString(account) + "%' or ";
            }
            sql.replace(sql.size() - 3, 3, ")");
        }
        if (passwords.size() > 0)
        {
            func::unique(passwords);
            sql += "(";
            for (auto& password : passwords)
            {
                sql += "password=" + func::toString(password) + " or ";
            }
            sql.replace(sql.size() - 3, 3, ")");
        }
        func::replace_all(sql, ")(", ") and (");
        return select(sql);
    }

    inline entity::Connect ConnectDao::insert(vector<int> keys, vector<int> accounts, int password) const
    {
        if (select_value(keys, accounts, password).size() == 0)
        {
            string _keys = func::encode(keys);
            string _accounts = func::encode(accounts);
            string _password = func::toString(password);
            execute("insert into record (key,account,password) values('"
                + _keys + "','" + _accounts + "'," + _password + ");");
            func::unique(keys);
            func::unique(accounts);
            for (auto& key : keys)
            {
                vdao->addref("key", key);
            }
            for (auto& account : accounts)
            {
                vdao->addref("account", account);
            }
            vdao->addref("password", password);
        }
        return select_value(keys, accounts, password).toElement();
    }

    inline entity::Connect ConnectDao::insert_connect(int id, vector<int> keys, vector<int> accounts) const
    {
        entity::Connect c = select_id(id);
        func::unique(keys);
        func::unique(accounts);
        for (auto& i : keys)
        {
            string s = " " + func::toString(i);
            int index = c.keys.find(s);
            if (index == string::npos)
            {
                vector<int> t = func::decode(c.keys);
                t.push_back(i);
                c.keys = func::encode(t);
                vdao->addref("key", i);
            }
        }
        for (auto& i : accounts)
        {
            string s = " " + func::toString(i);
            int index = c.accounts.find(s);
            if (index == string::npos)
            {
                vector<int> t = func::decode(c.accounts);
                t.push_back(i);
                c.accounts = func::encode(t);
                vdao->addref("account", i);
            }
        }
        string sql = "update record set key='" + c.keys + "' where id=" + func::toString(id) + ';';
        execute(sql);
        sql = "update record set account='" + c.accounts + "' where id=" + func::toString(id) + ';';
        execute(sql);
        return select_id(id);
    }

    inline entity::Result<entity::Connect> ConnectDao::_delete(string con)
    {
        entity::Result<entity::Connect> res = select(con);
        execute("delete from record where " + con + ';');
        return res;
    }

    inline void ConnectDao::init(BaseDao & dao)
    {
        BaseDao::init(dao);
    }

    inline void ConnectDao::setValueDao(ValueDao & dao)
    {
        vdao = &dao;
    }

    inline ValueDao* ConnectDao::getValueDao() const
    {
        return vdao;
    }

    inline void ConnectDao::destroy()
    {
        BaseDao::destroy();
    }

    inline entity::Connect ConnectDao::Delete_id(int id)
    {
        auto c = select_id(id);
        auto keys = func::decode(c.keys);
        for (auto& i : keys)
        {
            vdao->subref("key", i);
        }
        auto accounts = func::decode(c.accounts);
        for (auto& i : accounts)
        {
            vdao->subref("account", i);
        }
        vdao->subref("password", c.password);
        return _delete("id=" + func::toString(id)).toElement();
    }

    inline entity::Connect ConnectDao::Delete_connect(int id, vector<int> keys, vector<int> accounts)
    {
        entity::Connect c = select_id(id);
        func::unique(keys);
        func::unique(accounts);
        for (auto& i : keys)
        {
            string s = " " + func::toString(i);
            int index = c.keys.find(s);
            if (index != string::npos)
            {
                c.keys.erase(index, s.size());
                vdao->subref("key", i);
            }
        }
        for (auto& i : accounts)
        {
            string s = " " + func::toString(i);
            int index = c.accounts.find(s);
            if (index != string::npos)
            {
                c.accounts.erase(index, s.size());
                vdao->subref("account", i);
            }
        }
        string sql = "update record set key='" + c.keys + "' where id=" + func::toString(id) + ';';
        execute(sql);
        sql = "update record set account='" + c.accounts + "' where id=" + func::toString(id) + ';';
        execute(sql);
        return select_id(id);
    }

    inline entity::Connect ConnectDao::update(int id, string field, int old_id, int new_id)
    {
        if (field == "key")
        {
            return update_key(id, old_id, new_id);
        }
        else if (field == "account")
        {
            return update_account(id, old_id, new_id);
        }
        else if (field == "password")
        {
            return update_password(id, new_id);
        }
        else
        {
            throw "attribute error.\n";
        }
    }

    inline entity::Connect ConnectDao::update_key(int id, int old_id, int new_id)
    {
        entity::Connect c = select_id(id);
        string _old = " " + func::toString(old_id);
        string _new = " " + func::toString(new_id);
        int index = c.keys.find(_old);
        if (index != string::npos)
        {
            c.keys.erase(index, _old.size());
            vdao->subref("key", old_id);
        }
        else
        {
            throw "can't find the key with id" + _old;
        }
        vector<int> t = func::decode(c.keys);
        t.push_back(new_id);
        c.keys = func::encode(t);
        vdao->addref("key", new_id);

        string sql = "update record set key='" + c.keys + "' where id=" + func::toString(id) + ';';
        execute(sql);
        return select_id(id);
    }

    inline entity::Connect ConnectDao::update_account(int id, int old_id, int new_id)
    {
        entity::Connect c = select_id(id);
        string _old = " " + func::toString(old_id);
        string _new = " " + func::toString(new_id);
        int index = c.accounts.find(_old);
        if (index != string::npos)
        {
            c.accounts.erase(index, _old.size());
            vdao->subref("account", old_id);
        }
        else
        {
            throw "can't find the account with id" + _old;
        }
        vector<int> t = func::decode(c.accounts);
        t.push_back(new_id);
        c.accounts = func::encode(t);
        vdao->addref("account", new_id);

        string sql = "update record set account='" + c.accounts + "' where id=" + func::toString(id) + ';';
        execute(sql);
        return select_id(id);
    }

    inline entity::Connect ConnectDao::update_password(int id, int new_id)
    {
        entity::Connect c = select_id(id);
        vdao->subref("password", c.password);
        c.password = new_id;
        vdao->addref("password", new_id);
        string sql = "update record set password=" + func::toString(c.password) + " where id=" + func::toString(id) + ';';
        execute(sql);
        return select_id(id);
    }

    inline entity::Record convert(entity::Connect& c, ValueDao& dao)
    {
        entity::Record temp;
        temp.id = c.id;
        auto _keys = func::decode(c.keys);
        for (auto& i : _keys)
        {
            entity::KeyValue res = dao.select_id("key", i);
            temp.keys.push_back(res);
        }
        auto _account = func::decode(c.accounts);
        for (auto& i : _account)
        {
            entity::KeyValue res = dao.select_id("account", i);
            temp.accounts.push_back(res);
        }
        entity::KeyValue res = dao.select_id("password", c.password);
        temp.password = res;
        return temp;
    }
}

#endif
